// Copyright 2006 Mloofle Inc. All Rights Reserved.
import java.io.BufferedInputStream;
import java.io.DataInput;
import java.io.DataInputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;

/**
 * Represents the user's current command state.
 */
public class CommandState {

  private class IndexCommand implements Command {
    public boolean accept(Corpus corpus, String[] args) {
      if (args.length != 0) {
        System.out.println("Inappropriate request");
        return false;
      }
      corpus.printIndex();
      return true;
    }

    public String getName() {
      return "INDEX";
    }
  }

  private class NextCommand implements Command {
    public boolean accept(Corpus corpus, String[] args) {
      if (args.length != 0) {
        System.out.println("Inappropriate request");
        return false;
      }
      if (resultSet == null) {
        System.out.println("Inappropriate request");
        return false;
      }
      // ASSERT: resultSet != null
      return produceNextResult();
    }

    public String getName() {
      return "NEXT";
    }
  }
  private class QueryCommand implements Command {
    public boolean accept(Corpus corpus, String[] args) {
      try {
        QueryParser parser = new QueryParser(corpus.getWordAnalyzer());
        // parse and set the active query
        activeQuery = parser.parseQuery(args);
        // generate an active result set
        resultSet = corpus.runQuery(activeQuery);
        if (resultSet.size() == 0) {
          System.out.println("0 matches found");
        } else {
          System.out.println(resultSet.size() + " possible matches found");
        }
        return true;
      } catch (QueryParser.QuerySyntaxError e) {
        System.out.println("Malformed query");
        return false;

      }
    }

    public String getName() {
      return "QUERY";
    }
  }

  private class QuitCommand implements Command {
    public boolean accept(Corpus corpus, String[] args) {
      if (args.length != 0) {
        System.out.println("Inappropriate request");
        return false;
      }
      System.out.println("Successful command executions:");
      for (int i = 0; i < COMMAND.length; ++i) {
        Command cmd = COMMAND[i];
        int count = commandCounts[i];
        // Cheat: go ahead and give myself a +1 for the QUIT command :)
        if (cmd == this) {
          ++count;
        }
        System.out.println(cmd.getName() + ": " + count);
      }
      int totalTries = totalHits + falsePositives;
      if (totalTries > 0) {
        System.out.println("False positives: " + falsePositives + "/"
            + totalTries + " = " + (double) falsePositives / totalTries);
      }
      corpus.printSizeRatio();
      System.exit(0);
      return true;
    }

    public String getName() {
      return "QUIT";
    }
  }

  private class RefineCommand implements Command {
    public boolean accept(Corpus corpus, String[] args) {
      try {
        if (activeQuery == null) {
          System.out.println("Inappropriate request");
          return false;
        }
        QueryParser parser = new QueryParser(corpus.getWordAnalyzer());
        // parse the refinement
        Query refinement = parser.parseQuery(args);
        // new active query is the old active query AND the refinement
        activeQuery = new AndNode(activeQuery, refinement);
        // generate an active result set
        resultSet = corpus.runQuery(activeQuery);
        if (resultSet.size() == 0) {
          System.out.println("0 matches found");
        } else {
          System.out.println(resultSet.size() + " possible matches found");
        }
        return true;
      } catch (QueryParser.QuerySyntaxError e) {
        System.out.println("Malformed query");
        return false;

      }
    }

    public String getName() {
      return "REFINE";
    }
  }

  private class ShowCommand implements Command {
    public boolean accept(Corpus corpus, String[] args) {
      if (args.length != 0) {
        System.out.println("Inappropriate request");
        return false;
      }
      if (resultSet == null) {
        System.out.println("Inappropriate request");
        return false;
      }
      // ASSERT: resultSet != null
      resultSet.begin();
      // ASSERT: resultSet at beginning
      return produceNextResult();
    }

    public String getName() {
      return "SHOW";
    }
  }

  private class StatsCommand implements Command {
    public boolean accept(Corpus corpus, String[] args) {
      if (args.length != 0) {
        System.out.println("Inappropriate request");
        return false;
      }
      corpus.printStats();
      return true;
    }

    public String getName() {
      return "STATS";
    }
  }

  public CommandState(Corpus corpus) {
    this.corpus = corpus;
  }

  public void process(String cmdName, String[] cmdArgs) {
    for (int i = 0; i < COMMAND.length; ++i) {
      Command cmd = COMMAND[i];
      if (cmd.getName().equalsIgnoreCase(cmdName)) {
        if (cmd.accept(corpus, cmdArgs)) {
          // record this command
          commandCounts[i]++;
        } else {
          // the command itself should have printed an error message
        }
        return;
      }
    }
    System.out.println("Unknown command");
  }

  private String getTextForCorpusLine(CorpusFile corpusFile, int lineNum) {
    // PRE: file is non-null
    InputStream inStream = corpusFile.createInputStream();
    try {
      File file = new File(corpusFile.getFilePath());
      if (file.length() != corpusFile.getBytes()
          || file.lastModified() > corpus.getIndexCreationDate()) {
        // trigger "modified" error below
        throw new IOException();
      }

      inStream = new BufferedInputStream(inStream);
      DataInput dataInput = new DataInputStream(inStream);

      // Mapping from 1..lineNum rather than traditional 0..lineNum-1
      String line = null;
      for (int i = 1; i <= lineNum; ++i) {
        line = dataInput.readLine();
        if (line == null) {
          // catch below to handle error
          throw new IOException();
        }
      }
      return line;
    } catch (IOException e) {
      Util.errExit("Corpus file '" + corpusFile.getFilePath()
          + "' has been modified");
      return null;
    } finally {
      Util.close(inStream);
    }
    // POST: the line is returned, or the program is terminated
  }

  /**
   * Determines whether the possible match is a true match of the active query.
   */
  private boolean isTrueMatch(String lineText) {
    // PRE: lineText is non-null
    // PRE: activeQuery is non-null
    return activeQuery.matchesLineText(lineText, corpus.getCharacterAnalyzer());
    // POST: returns true if the active query matches the line text
  }

  /**
   * Used by both SHOW and NEXT to produce the next available result based on
   * the current position within the result set.
   */
  private boolean produceNextResult() {
    // PRE: resultSet non-null
    while (!resultSet.isEnd()) {
      PossibleMatch match = resultSet.iterate();
      CorpusFile file = match.getFile();
      int line = match.getLine();
      String lineText = getTextForCorpusLine(file, line);
      if (isTrueMatch(lineText)) {
        ++totalHits;
        System.out.println(match.toString());
        System.out.println(lineText);
        return true;
      } else {
        ++falsePositives;
      }
    }
    System.out.println("Inappropriate request");
    return false;
    // POST: returns true if a true match was found, otherwise false
    // POST: prints the location and text of the line matched
  }

  private Query activeQuery = null;

  private final Command[] COMMAND = new Command[] {
      new StatsCommand(), new IndexCommand(), new QueryCommand(),
      new RefineCommand(), new ShowCommand(), new NextCommand(),
      new QuitCommand()};

  private final int[] commandCounts = new int[COMMAND.length];
  private final Corpus corpus;
  private int falsePositives = 0;
  private ResultSet resultSet = null;
  private int totalHits = 0;

}
